/**
 ***************************************************************************************************
 *
 * @file app_adv.c
 *
 * @brief Advertising implementation
 *  Start ADV:
 *      1.create activity
 *      2.set ADV data
 *      3.set scan response data
 *      4.start activity
 *
 *  Stop ADV:
 *      1.stop activity
 *      2.delete activity
 * 
 * Copyright (C) Eker 2021
 *
 *
 ***************************************************************************************************
 */
#include "app.h"

#define APP_LOG_DOMAIN      "adv"
#define APP_LOG_LEVEL       APP_LOG_LEVEL_ADV
#include "app_log.h"

#define ADV_CHANNEL_MAP                 0x07    // each bit corresponds to a adv channel (37, 38, 39)

enum adv_state
{
    ADV_STATE_IDLE = 0,              // ADV activity doesn't exists

    ADV_STATE_CREATING,              // creating ADV activity
    ADV_STATE_CREATED,               // ADV activity created

    ADV_STATE_SETTING_ADV_DATA,      // setting ADV data

    ADV_STATE_SETTING_SCAN_RSP_DATA, // setting scan response data

    ADV_STATE_STARTING,              // starting ADV activity
    ADV_STATE_STARTED,               // ADV activity started

    ADV_STATE_STOPPING,              // stopping ADV activity
    ADV_STATE_STOPPED,               // ADV activity stopped

    ADV_STATE_DELETING,              // deleting ADV activity
    ADV_STATE_DELETED,               // ADV activtity deleted
};

enum adv_event
{
    ADV_EVENT_IDEL = 0,
    ADV_EVENT_START,
    ADV_EVENT_STOP,
};

enum adv_mode
{
    ADV_MODE_IDLE = 0,
    ADV_MODE_GENERAL,
    ADV_MODE_DIRECTED,
};

// ADV environment
typedef struct
{
    uint8_t operation;          // GAPM request operation
    uint8_t actv_idx;           // ADV activity index
    uint8_t adv_state;          // ADV state
    uint8_t adv_event_cur;      // current ADV control event
    uint8_t adv_event_next;     // next ADV control event
    uint8_t adv_mode;           // ADV mode
    adv_cfg_t adv_cfg;          // ADV config
    timeout_cb_t timeout_cb;    // ADV timeout callback
} adv_env_t;

static adv_env_t adv_env;

static bool is_advertising = false;

static void adv_data_parse(uint8_t *pdata, uint8_t len)
{
    int i = 0;
    do
    {
        if (pdata[i] == 0x02 && pdata[i+1] == 0x01)
        {
            adv_env.adv_cfg.adv_data_flag = pdata[i+2];
            LOG_DBG("ADV flag: 0x%02x", adv_env.adv_cfg.adv_data_flag);
        }
        else
        {
            memcpy(adv_env.adv_cfg.adv_data+i-3, pdata+i, 1+pdata[i]);
        }
        i = i + 1 + pdata[i];
    } while (i<len);
    adv_env.adv_cfg.adv_data_len = len - 3;
}

/**
 ***************************************************************************************************
 * @brief Create ADV activity
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_create_activity(void)
{
    LOG_VBS_FUNC();

    // set ADV state
    adv_env.adv_state = ADV_STATE_CREATING;

    // Prepare the GAPM_ACTIVITY_CREATE_CMD message
    struct gapm_activity_create_adv_cmd *p_cmd = KE_MSG_ALLOC(GAPM_ACTIVITY_CREATE_CMD,
                                                              TASK_GAPM, TASK_APP,
                                                              gapm_activity_create_adv_cmd);
    // gapm request operation
    p_cmd->operation = GAPM_CREATE_ADV_ACTIVITY;
    // local bt addr type
    p_cmd->own_addr_type = GAPM_STATIC_ADDR;
    // ADV type
    p_cmd->adv_param.type = GAPM_ADV_TYPE_LEGACY;
    // max tx power
    p_cmd->adv_param.max_tx_pwr = adv_env.adv_cfg.adv_power;
    // ADV filter policy
    p_cmd->adv_param.filter_pol = ADV_ALLOW_SCAN_ANY_CON_ANY;
#if (BLE_APP_WHITE_LIST)
    if (app_sec_get_bond_status())
    {
        p_cmd->adv_param.filter_pol = ADV_ALLOW_SCAN_WLST_CON_WLST;
    }
#endif
    // ADV channels that will be used
    p_cmd->adv_param.prim_cfg.chnl_map = adv_env.adv_cfg.channel_map;
    p_cmd->adv_param.prim_cfg.phy = GAP_PHY_LE_1MBPS;

    switch(adv_env.adv_mode)
    {
        case ADV_MODE_GENERAL:
        {
            LOG_DBG("ADV_MODE_GENERAL");
            // dicovery mode
            p_cmd->adv_param.disc_mode = GAPM_ADV_MODE_GEN_DISC;
            // ADV property
            p_cmd->adv_param.prop = GAPM_ADV_PROP_UNDIR_CONN_MASK;
            // ADV interval
            p_cmd->adv_param.prim_cfg.adv_intv_min = adv_env.adv_cfg.adv_interval;
            p_cmd->adv_param.prim_cfg.adv_intv_max = adv_env.adv_cfg.adv_interval;
        } break;

        case ADV_MODE_DIRECTED:
        {
            LOG_DBG("ADV_MODE_DIRECTED");
            // discovery mode
            p_cmd->adv_param.disc_mode = GAPM_ADV_MODE_NON_DISC;
            // ADV property
            if (adv_env.adv_cfg.dir_adv_timeout == 0)
                p_cmd->adv_param.prop = GAPM_ADV_PROP_DIR_CONN_HDC_MASK;
            else
                p_cmd->adv_param.prop = GAPM_ADV_PROP_DIR_CONN_MASK;
            // peer device address
            memcpy(&p_cmd->adv_param.peer_addr, &adv_env.adv_cfg.peer_addr, sizeof(struct gap_bdaddr));
            // ADV interval
            p_cmd->adv_param.prim_cfg.adv_intv_min = adv_env.adv_cfg.dir_adv_interval;
            p_cmd->adv_param.prim_cfg.adv_intv_max = adv_env.adv_cfg.dir_adv_interval;
        } break;

        default:
        {
            LOG_ERR("Unknown ADV mode!");
        } break;
    }

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_CREATE_ADV_ACTIVITY;
}

/**
 ***************************************************************************************************
 * @brief Delete ADV activity
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_delete_activity(void)
{
    LOG_VBS_FUNC();

    // set ADV state
    adv_env.adv_state = ADV_STATE_DELETING;

    // Prepare the GAPM_ACTIVITY_DELETE_CMD message
    struct gapm_activity_delete_cmd *p_cmd = KE_MSG_ALLOC(GAPM_ACTIVITY_DELETE_CMD,
                                                          TASK_GAPM, TASK_APP,
                                                          gapm_activity_delete_cmd);
    // gapm request operation
    p_cmd->operation = GAPM_DELETE_ACTIVITY;
    // activity index
    p_cmd->actv_idx = adv_env.actv_idx;

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_DELETE_ACTIVITY;
}

/**
 ***************************************************************************************************
 * @brief Start ADV activity
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_start_activity(void)
{
    LOG_VBS_FUNC();

    // set ADV state
    adv_env.adv_state = ADV_STATE_STARTING;

    // Prepare the GAPM_ACTIVITY_START_CMD message
    struct gapm_activity_start_cmd *p_cmd = KE_MSG_ALLOC(GAPM_ACTIVITY_START_CMD,
                                                         TASK_GAPM, TASK_APP,
                                                         gapm_activity_start_cmd);
    // gapm request operation
    p_cmd->operation = GAPM_START_ACTIVITY;
    // activity index
    p_cmd->actv_idx = adv_env.actv_idx;
    switch(adv_env.adv_mode)
    {
        case ADV_MODE_GENERAL:
        {
            LOG_DBG("ADV timeout: %d ms", adv_env.adv_cfg.adv_timeout);
            p_cmd->u_param.adv_add_param.duration = adv_env.adv_cfg.adv_timeout;
        } break;

        case ADV_MODE_DIRECTED:
        {
            LOG_DBG("ADV timeout: %d ms", adv_env.adv_cfg.dir_adv_timeout);
            p_cmd->u_param.adv_add_param.duration = adv_env.adv_cfg.dir_adv_timeout;
        } break;

        default:
        {
            LOG_ERR("Unknown ADV mode!");
        } break;
    }
    // max number of extended ADV events
    p_cmd->u_param.adv_add_param.max_adv_evt = 0;

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_START_ACTIVITY;
}

/**
 ***************************************************************************************************
 * @brief Stop ADV activity
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_stop_activity(void)
{
    LOG_VBS_FUNC();

    // set ADV state
    adv_env.adv_state = ADV_STATE_STOPPING;

    // Prepare the GAPM_ACTIVITY_STOP_CMD message
    struct gapm_activity_stop_cmd *p_cmd = KE_MSG_ALLOC(GAPM_ACTIVITY_STOP_CMD,
                                                        TASK_GAPM, TASK_APP,
                                                        gapm_activity_stop_cmd);
    // gapm request operation
    p_cmd->operation = GAPM_STOP_ACTIVITY;
    // activity index
    p_cmd->actv_idx = adv_env.actv_idx;

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_STOP_ACTIVITY;
}

/**
 ***************************************************************************************************
 * @brief Set ADV data
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_set_adv_data(void)
{
    LOG_VBS_FUNC();
    LOG_DBG_ARRAY_EX("ADV data", adv_env.adv_cfg.adv_data, adv_env.adv_cfg.adv_data_len);

    // set ADV state
    adv_env.adv_state = ADV_STATE_SETTING_ADV_DATA;

    // Prepare the GAPM_SET_ADV_DATA_CMD message
    struct gapm_set_adv_data_cmd *p_cmd = KE_MSG_ALLOC_DYN(GAPM_SET_ADV_DATA_CMD,
                                                           TASK_GAPM, TASK_APP,
                                                           gapm_set_adv_data_cmd,
                                                           adv_env.adv_cfg.adv_data_len);
    // gapm request operation
    p_cmd->operation = GAPM_SET_ADV_DATA;
    // activity index
    p_cmd->actv_idx = adv_env.actv_idx;

    // ADV data length
    p_cmd->length = adv_env.adv_cfg.adv_data_len;
    // ADV data
    memcpy(p_cmd->data, adv_env.adv_cfg.adv_data, adv_env.adv_cfg.adv_data_len);

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_SET_ADV_DATA;
}

/**
 ***************************************************************************************************
 * @brief Set scan response data
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_set_scan_rsp_data(void)
{
    LOG_VBS_FUNC();
    LOG_DBG_ARRAY_EX("Scan response data", adv_env.adv_cfg.res_data, adv_env.adv_cfg.res_data_len);

    // set ADV state
    adv_env.adv_state = ADV_STATE_SETTING_SCAN_RSP_DATA;

    // Prepare the GAPM_SET_ADV_DATA_CMD message
    struct gapm_set_adv_data_cmd *p_cmd = KE_MSG_ALLOC_DYN(GAPM_SET_ADV_DATA_CMD,
                                                           TASK_GAPM, TASK_APP,
                                                           gapm_set_adv_data_cmd,
                                                           adv_env.adv_cfg.res_data_len);

    // gapm request operation
    p_cmd->operation = GAPM_SET_SCAN_RSP_DATA;
    // activity index
    p_cmd->actv_idx = adv_env.actv_idx;

    // scan response data length
    p_cmd->length = adv_env.adv_cfg.res_data_len;
    // scan response data
    memcpy(p_cmd->data, adv_env.adv_cfg.res_data, adv_env.adv_cfg.res_data_len);

    // send the message
    ke_msg_send(p_cmd);

    // set operation type
    adv_env.operation = GAPM_SET_SCAN_RSP_DATA;
}

/**
 ***************************************************************************************************
 * @brief ADV state check
 *        Determin what control event will be excuted next.
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
static void adv_state_check(void)
{
    LOG_DBG("ADV state: %d", adv_env.adv_state);
    switch (adv_env.adv_state)
    {
        case ADV_STATE_IDLE:     // 0
        {
            if (adv_env.adv_event_next == ADV_EVENT_START)
                adv_create_activity();
        } break;

        case ADV_STATE_CREATING: // 1
        {
            adv_env.adv_event_cur = ADV_EVENT_STOP;
        } break;

        case ADV_STATE_CREATED:  //2
        {
            adv_env.adv_event_cur = ADV_EVENT_STOP;
            adv_delete_activity();
        } break;

        case ADV_STATE_SETTING_ADV_DATA: // 3
        case ADV_STATE_SETTING_SCAN_RSP_DATA: // 4
        case ADV_STATE_STARTING: // 5
        {
            adv_env.adv_event_cur = ADV_EVENT_STOP;
        } break;

        case ADV_STATE_STARTED:  // 6
        {
            adv_env.adv_event_cur = ADV_EVENT_STOP;
            adv_stop_activity();
        } break;

        case ADV_STATE_STOPPING: // 7
        case ADV_STATE_STOPPED:  // 8
        case ADV_STATE_DELETING: // 9
        case ADV_STATE_DELETED:  // 10
        {
            
        } break;

        default:
            break;
    }
}

/**
 ***************************************************************************************************
 * @brief ADV activity created handler
 *        This event will occur after call adv_create_activity()
 *
 * @param[in] p_param: data point
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_created_ind_handler(struct gapm_activity_created_ind const *p_param)
{
    if (p_param->actv_type == GAPM_ACTV_TYPE_ADV)
    {
        LOG_DBG("Activity created, actv_idx = 0x%02X", p_param->actv_idx);
        adv_env.actv_idx = p_param->actv_idx;
    }
}

/**
 ***************************************************************************************************
 * @brief ADV stopped handler
 *        This event will occur after call adv_stop_activity(), or when device is connected,
 *        or when advertising timeout.
 *
 * @param[in] p_param: data point
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_stopped_ind_handler(struct gapm_activity_stopped_ind const *p_param)
{
    if (p_param->actv_type == GAPM_ACTV_TYPE_ADV)
    {
        // reason 0x45 is ADV timeout
        LOG_DBG("Activity stopped, reason = 0x%02X", p_param->reason);
        LOG_INF("ADV stopped");

        adv_env.adv_state = ADV_STATE_STOPPED;
        if (ke_state_get(TASK_APP) == APPM_CONNECTED)
        {
            adv_env.adv_event_cur = ADV_EVENT_STOP;
            adv_env.adv_event_next = ADV_EVENT_IDEL;
        }
        adv_delete_activity();
    }
}

/**
 ***************************************************************************************************
 * @brief ADV operation complete handler
 *
 * @param[in] p_param: data point
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_cmp_evt_handler(struct gapm_cmp_evt const *p_param)
{
    LOG_DBG("operation = 0x%02X, status = 0x%02X", p_param->operation, p_param->status);
    if (p_param->status != GAP_ERR_NO_ERROR)
    {
        LOG_ERR("ADV error!!");
        return;
    }

    switch(p_param->operation)
    {
        case GAPM_CREATE_ADV_ACTIVITY: // 0xA0
        {
            // activity created
            adv_env.adv_state = ADV_STATE_CREATED;
            if (adv_env.adv_event_cur == ADV_EVENT_STOP)
            {
                adv_delete_activity();
            }
            else
            {
                if (adv_env.adv_mode == ADV_MODE_GENERAL)
                {
                    adv_set_adv_data();
                }
                else if (adv_env.adv_mode == ADV_MODE_DIRECTED)
                {
                    adv_start_activity();
                }
            }
        } break;

        case GAPM_START_ACTIVITY: // 0xA4
        {
            LOG_DBG("Activity started");
            adv_env.adv_state = ADV_STATE_STARTED;
            if (adv_env.adv_event_cur == ADV_EVENT_STOP)
            {
                adv_stop_activity();
            }
            else if (adv_env.adv_event_cur == ADV_EVENT_START)
            {
                LOG_INF("ADV started");
                is_advertising = true;
                adv_env.adv_event_next = ADV_EVENT_IDEL;
            }
        } break;

        case GAPM_STOP_ACTIVITY: // 0xA5
        {
            // this case only occur when user call adv_stop_activity()
        } break;

        case GAPM_DELETE_ACTIVITY: // 0xA7
        {
            LOG_DBG("Activity deleted");
            adv_env.adv_state = ADV_STATE_DELETED;
            if (ke_state_get(TASK_APP) == APPM_CONNECTED)
            {
                adv_env.adv_event_next = ADV_EVENT_IDEL;
                adv_env.timeout_cb = NULL;
            }
            if (adv_env.adv_event_next == ADV_EVENT_IDEL)
            {
                adv_env.adv_state = ADV_STATE_IDLE;
                adv_env.adv_event_cur = adv_env.adv_event_next = ADV_EVENT_IDEL;
                adv_env.adv_mode = ADV_MODE_IDLE;
                if (adv_env.timeout_cb != NULL)
                {
                    adv_env.timeout_cb();
                    adv_env.timeout_cb = NULL;
                }
                is_advertising = false;
            }
            else if (adv_env.adv_event_next == ADV_EVENT_START)
            {
                adv_env.adv_event_cur = ADV_EVENT_START;
                adv_create_activity();
            }
        } break;

        case GAPM_SET_ADV_DATA: // 0xA9
        {
            LOG_DBG("Set ADV data complete");
            if (adv_env.adv_event_cur == ADV_EVENT_STOP)
            {
                adv_delete_activity();
            }
            else if (adv_env.adv_event_cur == ADV_EVENT_START)
            {
                if (adv_env.adv_mode == ADV_MODE_GENERAL)
                {
                    if (adv_env.adv_cfg.res_data_len != 0)
                    {
                        adv_set_scan_rsp_data();
                    }
                    else
                    {
                        adv_start_activity();
                    }
                }
                else if (adv_env.adv_mode == ADV_MODE_DIRECTED)
                {
                    adv_start_activity();
                }
            }
        } break;

        case GAPM_SET_SCAN_RSP_DATA: // 0xAA
        {
            LOG_DBG("Set scan response data complete");
            if (adv_env.adv_event_cur == ADV_EVENT_STOP)
            {
                adv_delete_activity();
            }
            else if (adv_env.adv_event_cur == ADV_EVENT_START)
            {
                adv_start_activity();
            }
        } break;

        default:
        {
            LOG_ERR("Unknown operation!");
        } break;
    }
}

/**
 ***************************************************************************************************
 * @brief Start general ADV
 *        If it has set the general ADV parameters, call this function to start general ADV.
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_start_gen(void)
{
    adv_env.adv_mode = ADV_MODE_GENERAL;
    adv_env.adv_event_cur = adv_env.adv_event_next = ADV_EVENT_START;
    adv_state_check();
}

/**
 ***************************************************************************************************
 * @brief Start directed ADV
 *        If it has set the directed ADV parameters, call this function to start directed ADV.
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_start_dir(void)
{
    adv_env.adv_mode = ADV_MODE_DIRECTED;
    adv_env.adv_event_cur = adv_env.adv_event_next = ADV_EVENT_START;
    adv_state_check();
}

/**
 ***************************************************************************************************
 * @brief Stop ADV
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_stop(void)
{
    adv_env.adv_mode = ADV_MODE_IDLE;
    adv_env.adv_event_cur = ADV_EVENT_STOP;
    adv_env.adv_event_next = ADV_EVENT_IDEL;
    adv_state_check();
}

/**
 ***************************************************************************************************
 * @brief Set the ADV timeout callback function
 *
 * @param[in] timeout_cb: pointer to the callback funtion
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_set_timeout_cb(timeout_cb_t timeout_cb)
{
    adv_env.timeout_cb = timeout_cb;
}

/**
 ***************************************************************************************************
 * @brief ADV init
 *        Init whitelist and privacy if it's needed.
 *        Set the ADV parameters.
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_init(adv_cfg_t *adv_cfg)
{
    LOG_VBS_FUNC();
#if (BLE_APP_WHITE_LIST)
    appm_whl_init(true);
#endif
#if (BLE_APP_PRIVACY)
    appm_privacy_init(true);
#endif

    is_advertising = false;

    memset(&adv_env, 0, sizeof(adv_env_t));

    adv_data_parse(adv_cfg->adv_data, adv_cfg->adv_data_len);

    adv_env.adv_cfg.res_data_len = adv_cfg->res_data_len ;
    memcpy(adv_env.adv_cfg.res_data, adv_cfg->res_data, adv_env.adv_cfg.res_data_len);

    adv_env.adv_cfg.adv_interval = adv_cfg->adv_interval;
    adv_env.adv_cfg.adv_timeout = adv_cfg->adv_timeout;

    adv_env.adv_cfg.dir_adv_interval = adv_cfg->dir_adv_interval;
    adv_env.adv_cfg.dir_adv_timeout = adv_cfg->dir_adv_timeout;

    adv_env.adv_cfg.adv_type = adv_cfg->adv_type;
    adv_env.adv_cfg.adv_power = adv_cfg->adv_power;
    adv_env.adv_cfg.channel_map = adv_cfg->channel_map;
}

/**
 ***************************************************************************************************
 * @brief Set ADV data and scan response data
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_set_adv_data_and_scan_resp_data(adv_cfg_t *adv_cfg)
{
    LOG_VBS_FUNC();
    if (adv_cfg != NULL && adv_cfg->adv_data_len != 0)
    {
        adv_data_parse(adv_cfg->adv_data, adv_cfg->adv_data_len);
    }
    if (adv_cfg != NULL && adv_cfg->res_data_len != 0)
    {
        adv_env.adv_cfg.res_data_len = adv_cfg->res_data_len;
        memcpy(adv_env.adv_cfg.res_data, adv_cfg->res_data, adv_env.adv_cfg.res_data_len);
    }
    if (is_advertising)
        app_adv_start_gen();
}

/**
 ***************************************************************************************************
 * @brief Set scan response data
 *
 * @param   void
 * 
 * @return  void
 ***************************************************************************************************
 */
void app_adv_set_adv_param(adv_cfg_t *adv_cfg)
{
    LOG_VBS_FUNC();
    adv_env.adv_cfg.adv_interval = adv_cfg->adv_interval;
    // adv_env.adv_cfg.adv_timeout = adv_cfg->adv_timeout;
    adv_env.adv_cfg.adv_type = adv_cfg->adv_type;
    // adv_env.adv_cfg.adv_power = adv_cfg->adv_power;
    // adv_env.adv_cfg.channel_map = adv_cfg->channel_map;
}
